#include "Acceptor.h"
#include "Sockaddr.h"
#include "eventloop/Eventloop.h"
#include "eventloop/IOchannel.h"
#include "log/Logger.h"
#include "sock.h"
#include "utility/assertion.h"
#include "wrap/fcntl.h"
#include "wrap/unistd.h"


namespace _ {

static int reserve_fd() {
    return Wrap::open_("/dev/null", Wrap::o_rdonly() | Wrap::o_cloexec());
}

}

Acceptor::Acceptor(Eventloop &loop, const Sockaddr &addr, bool resusePort):
    _newConnectionCallback{},
    _loop{loop},
    _channel{},
    _socket{Sock::create_nonblock_socket(addr.family())},
    _fdReservered{_::reserve_fd()},
    _listening{false} {
    assert(_fdReservered >= 0);
    _socket.reuse_addr(true);
    _socket.reuse_port(true);
    _socket.bind(addr);
    _channel = std::make_unique<IOchannel>(loop, _socket.fd(), __func__ + addr.IP_port());
    _channel->set_read_callback(
                [this](Utility::timepoint /*t*/){ _read_callback(); });
}

Acceptor::~Acceptor() {
    _channel->disable();
    _channel->remove();
    Wrap::close_(_fdReservered);
}

void Acceptor::set_new_connection_callback(const NewConnectionCallback &callback) {
    if (!callback) {
        _newConnectionCallback.reset();
        return;
    }
    _newConnectionCallback = callback;
}

void Acceptor::listen() {
    _loop.ASSERT_IN_THIS_THREAD();
    _listening = true;
    _socket.listen();
    _channel->enable_reading();
}

void Acceptor::_accept_error() {
    LOG_SYSERROR << "accept failed";
    if (errno != EMFILE) {
        return;
    }
    Wrap::close_(_fdReservered);
    _fdReservered = Wrap::accept_(_socket.fd(), nullptr, nullptr);
    Wrap::close_(_fdReservered);
    _fdReservered = _::reserve_fd();
}

void Acceptor::_read_callback() {
    _loop.ASSERT_IN_THIS_THREAD();
    Sockaddr peer{};
    const auto fd = _socket.accept(peer);
    if (fd < 0) {
        _accept_error();
        return;
    }
    if (!_newConnectionCallback.has_value()) {
        Wrap::close_(fd);
        return;
    }
    _newConnectionCallback.value()(std::make_unique<Socket>(fd), peer);
}
